package com.keray.web.push.worker;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.keray.web.push.ClientData;
import com.keray.web.push.MessageType;
import com.keray.web.push.config.WebSocketConfig;
import com.keray.web.push.store.SocketDataStore;
import com.keray.web.push.store.WorkerStore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;

/**
 * @author by keray
 * date:2021/7/23 9:56 上午
 */
@Slf4j
@ServerEndpoint(value = "/{service}/{uid}", configurator = WebSocketConfig.class)
@Controller
public class WebSocketLink {

    @Resource(name = "memoryStore")
    private SocketDataStore socketDataStore;


    /**
     * <p>
     * <h3>作者 keray</h3>
     * <h3>时间： 2021/7/23 10:32 上午</h3>
     * service+uid 一比一 session
     * service+group 一比多 session
     * service 对应全部 session
     * </p>
     *
     * @return <p> {@link } </p>
     * @throws
     */
    @OnOpen
    public void onOpen(Session session) {
        try {
            ClientData data = sessionGetClientData(session);
            try {
                workerExec(worker -> worker.onOpen(session, data), session);
                int res = socketDataStore.insertSession(session, data.getService(), data.getUid());
                if (res == -1) {
                    session.getBasicRemote().sendText("SYSTEM_CON_FULL");
                    session.close();
                } else if (res == -2) {
                    session.getBasicRemote().sendText("SYSTEM_SAVE_ERROR");
                    session.close();
                }
                syncSend("SYSTEM_OK", session);
            } catch (RuntimeException e) {
                log.error("socket建立连接失败：", e);
                if ("no worker".equals(e.getMessage())) {
                    session.getBasicRemote().sendText("SYSTEM_WORKER_NO");
                    session.close();
                } else {
                    throw e;
                }
            }
        } catch (Exception e) {
            log.error("openFail:", e);
            try {
                session.close();
            } catch (IOException ignore) {
            }
        }
    }

    @OnClose
    public void onClose(Session session) {
        ClientData data = sessionGetClientData(session);
        workerExec(worker -> worker.onClose(session, data), session);
        socketDataStore.removeSession(session, data.getService(), data.getUid());
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        workerExec(worker -> worker.onError(session, throwable, sessionGetClientData(session)), session);
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        ClientData data = sessionGetClientData(session);
        if (message.startsWith("group-link:")) {
            String groupStr = message.substring(11);
            if (StrUtil.isBlank(groupStr)) return;
            String[] groupIds = groupStr.split(",");
            log.info("关联机构：data={}, groupIds={}", data, Arrays.toString(groupIds));
            for (String groupId : groupIds) {
                socketDataStore.groupSessionLink(data.getService(), groupId, data.getUid());
            }
            try {
                syncSend("SYSTEM_OK", session);
            } catch (IOException e) {
                log.error("group-link fail:", e);
                socketDataStore.removeSession(session, data.getService(), data.getUid());
            }
            return;
        }
        workerExec(worker -> worker.onMessage(message, session, data), session);
    }

    private void workerExec(Consumer<SocketWorker> call, Session session) {
        List<SocketWorker> workers = getWorker(session);
        if (CollUtil.isEmpty(workers)) {
            throw new RuntimeException("no worker");
        }
        for (SocketWorker worker : workers) {
            call.accept(worker);
        }
    }


    private void syncSend(String message, Session session) throws IOException {
        session.getBasicRemote().sendText(message);
    }

    private Map<String, Object> pathParamsMap(Session session) {
        return (Map<String, Object>) session.getUserProperties().get("org.apache.tomcat.websocket.pojo.PojoEndpoint.pathParams");
    }


    private ClientData sessionGetClientData(Session session) {
        var map = pathParamsMap(session);
        return new ClientData(map.get("service").toString(),
                null,
                map.get("uid").toString()
        );
    }


    private List<SocketWorker> getWorker(Session session) {
        List<String> types = session.getRequestParameterMap().get("type");
        if (CollUtil.isEmpty(types)) {
            return null;
        }
        return types.stream().map(type -> {
            try {
                return WorkerStore.MAP.get(MessageType.valueOf(type));
            } catch (Exception e) {
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }
}
